﻿
using Red.Controls.Utils;
using Red.Controls.ViewModels;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Data;
using System.Reflection;

namespace Red.Controls.DataDriven
{
    /// <summary>
    ///     Extensions for Interop controls
    /// </summary>
    public static class InteropExtension
    {
        #region Path property

        /// <summary>
        ///     Interop path, relative to parent's DataItemEngine (set as parent's DataContext).
        ///     Changing this property will automatically set the item's DataContext to an appropriate sub-item.
        /// </summary>
        public static readonly DependencyProperty PathProperty = DependencyProperty.RegisterAttached( 
            "Path", typeof( string ), typeof( FrameworkElement ), new PropertyMetadata( "", HandleInteropPathChanged ) );

        public static string GetPath( DependencyObject element )
        {
            return (string)element.GetValue( PathProperty );
        }

        public static void SetPath( DependencyObject element, string value )
        {
            element.SetValue( PathProperty, value );
        }

        #endregion Path property

        #region RebuildOnChange property

        /// <summary>
        ///     If set to true, causes bound DataItem to rebuild itself (repopulate children list) instead of only refreshing
        ///     its values. Set it if you expect the children structure to be dynamic. Default is 'false'.
        /// </summary>
        public static readonly DependencyProperty RebuildOnChangeProperty = DependencyProperty.RegisterAttached(
            "RebuildOnChange", typeof( bool ), typeof( FrameworkElement ), new PropertyMetadata( false ) );

        public static bool GetRebuildOnChange( DependencyObject element )
        {
            return (bool)element.GetValue( RebuildOnChangeProperty );
        }

        public static void SetRebuildOnChange( DependencyObject element, bool value )
        {
            element.SetValue( RebuildOnChangeProperty, value );
        }

        #endregion RebuildOnChange property

        #region BindingTarget property

        /// <summary>
        /// Changing this property will automatically create a two way binding expression: InteropDataItem.Value - TargetProperty.
        /// Usage example: 
        ///     TextBlock data:Interop.BindingTarget="TextProperty" data:Interop.Path="text" data:Interop.RebuildOnChange="True"
        /// </summary>
        public static readonly DependencyProperty BindingTargetProperty = DependencyProperty.RegisterAttached( 
            "BindingTarget", typeof( string ), typeof( InteropExtension ), new PropertyMetadata( String.Empty ) );

        public static string GetBindingTarget( DependencyObject obj )
        {
            return (string)obj.GetValue( BindingTargetProperty );
        }

        public static void SetBindingTarget( DependencyObject obj, string value )
        {
            obj.SetValue( BindingTargetProperty, value );
        }    

        #endregion

        #region ExpandByDefault property

        /// <summary>
        ///     All controls bound with InteropExtension.PathProperty will have their items expanded automatically,
        ///     unless you set this property to 'false'.
        /// </summary>
        public static readonly DependencyProperty ExpandByDefaultProperty = DependencyProperty.RegisterAttached(
            "ExpandByDefault", typeof( bool ), typeof( FrameworkElement ), new PropertyMetadata( true ) );

        public static bool GetExpandByDefault( DependencyObject element )
        {
            return (bool)element.GetValue( ExpandByDefaultProperty );
        }

        public static void SetExpandByDefault( DependencyObject element, bool value )
        {
            element.SetValue( ExpandByDefaultProperty, value );
        }

        #endregion ExpandByDefault property

        #region Event handlers
        
        private static async void HandleInteropPathChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e )
        {
            var element = obj as FrameworkElement;
            string newPath = (string)e.NewValue;

            if ( element.IsLoaded )
            {   // If it's already loaded, rebind now, otherwise it will be handled by the event below
                await Rebind( element, newPath );
            }
            else
            { // if we're not loaded yet, prevent from displaying "improper" derived parent DataContext
                element.DataContext = null;
            }

            element.Loaded += async ( _s, _e ) =>
            {   // Changing the parent will also cause the "Loaded" handler to be called - and because we are dependent on 
                // parent's DataContext, we want to be ready for this
                await Rebind( (FrameworkElement)_s, newPath );
            };
        }

        private static async Task Rebind( FrameworkElement element, string interopPath )
        {
            var parent = VisualTreeHelper.GetParent( element );
            var ancestor = Helpers.FindVisualAncestorOfType<FrameworkElement>( parent );
            if ( ancestor != null )
            {
                DependencyPropertyChangedEventHandler onParentItemChanged = async ( s, e ) =>
                {
                    await Rebind( element, interopPath );
                };
                ancestor.DataContextChanged -= onParentItemChanged; // in case it was added to the same parent before
                ancestor.DataContextChanged += onParentItemChanged;

                var parentItem = ancestor.DataContext as DataItem;
                if ( parentItem != null )
                {
                    if ( !String.IsNullOrEmpty( interopPath ) ) // if it's empty, it's just inherited by the standard WPF mechanism.
                    {
                        // NOTE: it's important to plug-in into EXISTING hierarchy instead of creating a new item here, 
                        // so that the controls appears on the parent's children list and if, for example - the parent gets 
                        // refreshed recursively, this control also gets refreshed.
                        DataItem item = await parentItem.DigUpSubItemAsync( interopPath );
                        item.RebuildOnChange = (bool)element.GetValue( RebuildOnChangeProperty );
                        //item.IsExpanded = (bool)element.GetValue( ExpandByDefaultProperty );
                        element.DataContext = item;

                        // Creates Interop <=> TargetProperty binding
                        string propertyName = (string)element.GetValue( BindingTargetProperty );
                        if ( propertyName != String.Empty )
                        {
                            Type type = element.GetType();
                            FieldInfo field = type.GetField( propertyName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy );
                            if ( field != null )
                            {
                                DependencyProperty dependecyProperty = (DependencyProperty)field.GetValue( element );
                                if ( dependecyProperty != null )
                                {
                                    element.SetBinding( dependecyProperty, new Binding( "Value" )
                                        {
                                            Mode = BindingMode.TwoWay,
                                            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                                        } );
                                }
                            }
                        }
                    }
                }
            }
        }
        
        #endregion Event handlers
    }
}